/*
Copyright 2021 LuJun

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished 
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/// <reference path="../base/lcc.ts" />
/// <reference path="../base/loading-module.ts" />
/// <reference path="../base/variable.ts" />
/// <reference path="../utils/js.ts" />

namespace lcc {

const TAG = "lcc/config/config-manager.ts";

/**
 * @zh 
 * 是否使用远程配置<br/>
 * 如果开启，并且配置的JSON的根中有`remoteconfig`字段<br/>
 * 那么管理器在加载的时候会下载`remoteconfig`指向的JSON文件，并与当前配置合并  
 */
export let USE_REMOTE_CONFIG = true;

/**
 * @zh
 * 下载远程配置`remoteconfig`文件的超时时间
 */
export let REMOTE_CONFIG_TIMEOUT = 5;

/**
 * @zh
 * 配置对象映射表<br/>
 * 在加载完对应配置后，可以直接通过这个变量访问配置数据<br/>
 * ```
 *      lcc.configMgr.loadConfig('config/test');
 *      let testConfig = CONFIGS.test;
 *      // let testConfig = CONFIGS['test'];
 * ```
 */
export let CONFIGS:{[key:string]:any} = utils.js.createMap();

/**
 * @zh
 * 加载配置选项
 */
export interface LoadConfigOption {
	/**
	 * @zh
	 * 配置名称
	 */
	name?:string;

	/**
	 * @zh
	 * 包对象
	 */
	bundle?:__cc__.AssetManager.Bundle;
}

/**
 * @zh
 * 配置管理器<br/>
 * 主要用于管理程序的配置与远程配置<br/>
 * 可以对本地配置表中的变量进行替换，并且可以合并配置  
 */
export class ConfigManager extends LoadingModule {
    /**
     * 管理器实例
     */
    private static _instance:ConfigManager;

    /**
     * 单例获取函数
     */
    public static getInstance() : ConfigManager  {
        if(!ConfigManager._instance){
            ConfigManager._instance = new ConfigManager();
        }
        return ConfigManager._instance;
    }

    /**
     * @zh
     * 获得所有配置<br/>
     * 也可以直接使用`CONFIGS`
     */
    public getConfigs(){
        return CONFIGS;
    }
    
    /**
     * @zh
     * 获得对应配置<br/>
     * 也可以直接使用`CONFIGS[key]`
     */
    public getConfig<T>(key:string){
        return CONFIGS[key] as T;
    }
	
	/**
     * @zh
	 * 清空所有配置
	 */
    public clearAll(){
        for(let cname in Object.keys(CONFIGS)){
            delete CONFIGS[cname];
        }
    }
    
    /**
     * @zh
     * 清空指定配置<br/>
     * 这个方法只清除配置表的内容，保留配置表<br/>
     * 也可以直接使用`delete CONFIGS[cname];`删除配置表
     * 
     * @param cname - 配置名称
     */
    public clearConfig(cname:string){
        let config = CONFIGS[cname];
        if(config){
            for(let key in Object.keys(config)){
                delete config[key];
            }
        }
    }
    
    /**
     * @zh
     * 加载配置<br/>
	 * 用法：<br/>
	 * ```
	 * 		// 加载resources目录下的配置
	 * 		lcc.configMgr.loadConfig('config/test');
	 * 		// 获得加载的配置
	 * 		lcc.configMgr.getConfig('test');
	 * 
	 * 		// 加载resources目录下的配置，并且重置名称
	 * 		lcc.configMgr.loadConfig('config/test', 'newtest');
	 * 		// 获得加载的配置
	 * 		lcc.configMgr.getConfig('newtest');
	 * 
	 * 		// 加载game子包下的配置，并且重置名称
	 * 		lcc.configMgr.loadConfig('config/test', { name : 'newtest', bundle:cc.assetManager.loadBundle('game') });
	 * 		// 获得加载的配置
	 * 		lcc.configMgr.getConfig('newtest');
	 * ```
	 * 
     * @param jfile - JSON文件路径
     * @param cname - 配置名称
     */
    public async loadConfig(jfile:string, nameOrOption?:string|LoadConfigOption){
		this.setLoading(true);
		let option:LoadConfigOption;
		if(typeof nameOrOption == 'string'){
			option = {
				name : nameOrOption,
			};
		}else{
			option = nameOrOption;
		}
		let localconf = await utils.asset.loadJson(jfile, option.bundle);
		if(localconf){
			let cname = option.name || utils.fs.basename(jfile);
			this.clearConfig(cname);
            let content = JSON.stringify(localconf);
            content = parseVariables(content, { localpath : utils.fs.dirname(jfile) });
			localconf = JSON.parse(content);
			DEBUG.CONFIG && __cc__.log(TAG, `local config ${cname} : ${JSON.stringify(localconf)}`);
			let config = CONFIGS[cname];
			if(!config){
				config = utils.js.createMap();
				CONFIGS[cname] = config;
			}
			utils.js.mergeMap(config, localconf, { tag : TAG, source : jfile });
			if(USE_REMOTE_CONFIG && localconf.remoteconfig){
				let remoteconf = await http.getJson(localconf.remoteconfig, REMOTE_CONFIG_TIMEOUT);
				if(remoteconf){
					DEBUG.CONFIG && __cc__.log(TAG, `remote config ${cname} : ${JSON.stringify(remoteconf)}`);
					utils.js.mergeMap(config, remoteconf, { tag : TAG, source : localconf.remoteconfig });
				}
			}
		}else{
			__cc__.error(TAG, `loadConfig file '${jfile}' not found!!!`);
		}
		this.setLoading(false);
    }
};

/**
 * @zh
 * 配置管理器对象
 */
export const configMgr = ConfigManager.getInstance();

}
